// LuxSpectralis.c
// RGB LED color wash
// written by Dale Wheat - 2 December 2008
// modified by Joe Hohertz  - 20 January 2010 (port to attiny85, while trying to keep source portable)

// device = ATtiny85
// clock = internal 16 MHz oscillator, longest startup time
// max ISP voltage 4.5 VDC - sometimes needs to be less (3.0 V)
// BOD = 1.8V

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/pgmspace.h>
#include "common.h"
#include "vm.h"
#include "delay.h"
#include "prand.h"
#include "sleep.h"

typedef enum {
	MODE_POWER_ON = 0, // power on behavior
	MODE_WHITE_5MIN = 0,
	MODE_WHITE_30MIN,
	MODE_WHITE_25,
	MODE_WHITE_50,
	MODE_WHITE_100,
	MODE_RED_25,
	MODE_RED_100,
	MODE_GREEN_25,
	MODE_GREEN_100,
	MODE_BLUE_25,
	MODE_BLUE_100,
	MODE_YELLOW_25,
	MODE_YELLOW_100,
	MODE_AQUA_25,
	MODE_AQUA_100,
	MODE_VIOLET_25,
	MODE_VIOLET_100,
	MODE_WASH1_25,
	MODE_WASH1_100,
	MODE_WASH2_25,
	MODE_WASH2_100,
	MODE_WASH3_25,
	MODE_WASH3_100,
	MODE_WASH4_25,
	MODE_WASH4_100,
	MODE_FLASH_WHITE,
	MODE_FLASH_RED,
	MODE_FLASH_GREEN,
	MODE_FLASH_BLUE,
	MODE_FLASH_RED_GREEN,
	MODE_FLASH_RED_BLUE,
	MODE_FLASH_GREEN_BLUE,
	MODE_BLINK_WHITE,
	MODE_BLINK_RED,
	MODE_BLINK_GREEN,
	MODE_BLINK_BLUE,
	MODE_MAX // off
} MODE;

const uint8_t timedLightProg[35] PROGMEM = {
	0x70, 0xfc, 0xbe, 0x00, 0xbd, 0xff, 0x55, 0xed, 0x65, 0x0f,
	0x7f, 0x1f, 0x72, 0x0e, 0xc6, 0x7f, 0xff, 0x7f, 0xff, 0xbd,
	0x00, 0x55, 0xed, 0x65, 0x20, 0x7f, 0x9f, 0x72, 0x0e, 0x7f,
	0x0f, 0xd5, 0x70, 0x00, 0x02
};
const uint8_t staticRgbProg[4] PROGMEM = {
	0x57, 0xfe, 0xdc, 0xc0
};
const uint8_t colorWashProg[24] PROGMEM = {
	0x7f, 0x89, 0x57, 0xab, 0xc8, 0x55, 0xad, 0x63, 0x0b, 0x5f,
        0xad, 0x55, 0xbe, 0x63, 0x11, 0x5f, 0xae, 0x55, 0xcf, 0x63,
	0x17, 0x5f, 0xaf, 0xc0
};
const uint8_t flashProg[13] PROGMEM = {
	0x78, 0xff, 0x56, 0xfe, 0xd0, 0x7f, 0x8c, 0x56, 0xba, 0x90,
	0x7f, 0x88, 0xc2
};

struct staticRgbElement {
	uint8_t red;       // brightness value for Red LED (0 to 255)
	uint8_t green;     // brightness value for Green LED (0 to 255)
	uint8_t blue;      // brightness value for Blue LED (0 to 255)
	uint8_t bright;    // brightness value for ALL (0 to 255)
} const staticRgbTab[] PROGMEM = {
  { 0xFF, 0xFF, 0xFF, 0x40 },			// White @ 25%
  { 0xFF, 0xFF, 0xFF, 0x80 },			// White @ 50%
  { 0xFF, 0xFF, 0xFF, 0xFF },			// White @ 100%
  { 0xFF, 0x00, 0x00, 0x40 },			// Red @ 25%
  { 0xFF, 0x00, 0x00, 0xFF },			// Red @ 100%
  { 0x00, 0xFF, 0x00, 0x40 },			// Green @ 25%
  { 0x00, 0xFF, 0x00, 0xFF },			// Green @ 100%
  { 0x00, 0x00, 0xFF, 0x40 },			// Blue @ 25%
  { 0x00, 0x00, 0xFF, 0xFF },			// Blue @ 100%
  { 0xFF, 0xFF, 0x00, 0x40 },			// Yellow @ 25%
  { 0xFF, 0xFF, 0x00, 0xFF },			// Yellow @ 100%
  { 0x00, 0xFF, 0xFF, 0x40 },			// Aqua @ 25%
  { 0x00, 0xFF, 0xFF, 0xFF },			// Aqua @ 100%
  { 0xFF, 0x00, 0xFF, 0x40 },			// Violet @ 25%
  { 0xFF, 0x00, 0xFF, 0xFF }			// Violet @ 100%
};

struct colorWashRgbElement {
	uint8_t delay;       // delay value
	uint8_t bright;    // brightness value for ALL (0 to 255)
} const colorWashRgbTab[] PROGMEM = {
  { 1, 0x40 },		// fast, dim
  { 1, 0xFF },		// fast, bright
  { 3, 0x40 },		// moderate, dim
  { 3, 0xFF },		// moderate, bright
  { 10, 0x40 },		// slow, dim
  { 10, 0xFF },		// slow, bright
  { 100, 0x40 },	// really slow, dim
  { 100, 0xFF }		// really slow, bright
};

struct flashRgbElement {
	uint8_t red1;       // first brightness value for Red LED (0 to 255)
	uint8_t green1;     // first brightness value for Green LED (0 to 255)
	uint8_t blue1;      // first brightness value for Blue LED (0 to 255)
	uint8_t time1;      // first delay
	uint8_t red2;       // second brightness value for Red LED (0 to 255)
	uint8_t green2;     // second brightness value for Green LED (0 to 255)
	uint8_t blue2;      // second brightness value for Blue LED (0 to 255)
	uint8_t time2;      // delay
} const flashRgbTab[] PROGMEM = {
  { 0xFF, 0xFF, 0xFF, 73, 0x00, 0x00, 0x00, 73 }, // flash white
  { 0xFF, 0x00, 0x00, 73, 0x00, 0x00, 0x00, 73 }, // flash red
  { 0x00, 0xFF, 0x00, 73, 0x00, 0x00, 0x00, 73 }, // flash green
  { 0x00, 0x00, 0xFF, 73, 0x00, 0x00, 0x00, 73 }, // flash blue
  { 0xFF, 0x00, 0x00, 73, 0x00, 0xFF, 0x00, 73 }, // flash red & green
  { 0xFF, 0x00, 0x00, 73, 0x00, 0x00, 0xFF, 73 }, // flash red & blue
  { 0x00, 0xFF, 0x00, 73, 0x00, 0x00, 0xFF, 73 }, // flash green & blue
  { 0xFF, 0xFF, 0xFF, 2, 0x00, 0x00, 0x00, 144 }, // blink white
  { 0xFF, 0x00, 0x00, 5, 0x00, 0x00, 0x00, 141 }, // blink red
  { 0x00, 0xFF, 0x00, 5, 0x00, 0x00, 0x00, 141 }, // blink green
  { 0x00, 0x00, 0xFF, 5, 0x00, 0x00, 0x00, 141 }  // blink blue
};

// this is used to preserve the value across reset
volatile MODE mode __attribute__ ((section (".noinit")));

///////////////////////////////////////////////////////////////////////////////
// init() - initialize everything
// note:  this "function" is in section .init3 so it is executed before main()
///////////////////////////////////////////////////////////////////////////////

void init(void) __attribute__ ((naked, section(".init3")));
void init(void) {
	// determine cause of device reset;  act accordingly
	// a cute trick used to somehow keep state of mode across resets (see above) --joe
	if(bit_is_set(MCUSR, PORF)) {
		// fresh power on
		mode = MODE_POWER_ON; // power on!
	} else if(bit_is_set(MCUSR, EXTRF)) {
		// user triggered the reset button, used here to advance modes
		mode++; // advance mode
		if(mode > MODE_MAX) {
			mode = MODE_POWER_ON; // reset mode
		}
	}

	MCUSR = 0; // reset bits so the result on next reset is reliable

	// initialize ATtiny2313 input & output ports
	PORTA = 1<<PORTA2;		// set reset high? XXX why should we need this? ahh pull ups
	//PORTB = 0x00;
	//PORTD = 0x00;

	// setup Timer/Counter0 Control Registers
	TCCR0B = 0<<FOC0A | 0<<FOC0B | 0<<WGM02 | 0<<CS02 | 0<<CS01 | 1<<CS00;
	TCCR0A = 1<<COM0A1 | 0<<COM0A0 | 1<<COM0B1 | 0<<COM0B0 | 1<<WGM01 | 1<<WGM00;
	// setup Timer/Counter1 Control Registers
	TCCR1B = 0<<ICNC1 | 0<<ICES1 | 0 << WGM13 | 1<<WGM12 | 0<<CS12 | 0<<CS11 | 1<<CS10;
	TCCR1A = 1<<COM0A1 | 0<<COM0A0 | 1<<COM0B1 | 0<<COM0B0 | 0<<WGM01 | 1<<WGM00;
	TCCR1C = 0x00;		// always should do after setting TCCR1A
	GTCCR |= _BV(PSR10);	// reset/sync the timer prescaler now we're all setup to run, before we start using it
	// set our data directions
	DDRB = 1<<DDRLEDR | 1<<DDRLEDG | 1<<DDRLEDB;
	DDRD = 1<<DDRLEDI;
	// initialize interrupts.
	TIMSK = 0<<OCIE0B | 0<<OCIE0A | 1<<TOIE0; // interrupts
	sei(); // enable global interrupts
}

///////////////////////////////////////////////////////////////////////////////
// main() - main program function
///////////////////////////////////////////////////////////////////////////////

void main(void) {
	wipe_program();
	if (mode >= MODE_WHITE_5MIN && mode <= MODE_WHITE_30MIN) {
		// timed_light type mode, this trick works for our two modes for now:
		reg[15] = (mode == MODE_WHITE_5MIN) ? 42 : 255;
		memcpy_P(program, &timedLightProg, sizeof(timedLightProg) );
	} else if (mode >= MODE_WHITE_25 && mode <= MODE_VIOLET_100) {
		// XXX lookup values from table, set registers, load 'static' program in
		int tabidx = mode - MODE_WHITE_25;
		reg[15] = pgm_read_byte(&staticRgbTab[tabidx].red);
		reg[14] = pgm_read_byte(&staticRgbTab[tabidx].green);
		reg[13] = pgm_read_byte(&staticRgbTab[tabidx].blue);
		reg[12] = pgm_read_byte(&staticRgbTab[tabidx].bright);
		memcpy_P(program, &staticRgbProg, sizeof(staticRgbProg) );
	} else if (mode >= MODE_WASH1_25 && mode <= MODE_WASH4_100) {
		// XXX lookup values from table, set registers, load 'wash' program in
		int tabidx = mode - MODE_WASH1_25;
		reg[9] = pgm_read_byte(&colorWashRgbTab[tabidx].delay);
		reg[8] = pgm_read_byte(&colorWashRgbTab[tabidx].bright);
		memcpy_P(program, &colorWashProg, sizeof(colorWashProg) );
	} else if (mode >= MODE_FLASH_WHITE && mode <= MODE_BLINK_BLUE) {
		// XXX lookup values from table, set registers, load 'wash' program in
		int tabidx = mode - MODE_FLASH_WHITE;
		reg[15] = pgm_read_byte(&flashRgbTab[tabidx].red1);
		reg[14] = pgm_read_byte(&flashRgbTab[tabidx].green1);
		reg[13] = pgm_read_byte(&flashRgbTab[tabidx].blue1);
		reg[12] = pgm_read_byte(&flashRgbTab[tabidx].time1);
		reg[11] = pgm_read_byte(&flashRgbTab[tabidx].red2);
		reg[10] = pgm_read_byte(&flashRgbTab[tabidx].green2);
		reg[9] = pgm_read_byte(&flashRgbTab[tabidx].blue2);
		reg[8] = pgm_read_byte(&flashRgbTab[tabidx].time2);
		memcpy_P(program, &flashProg, sizeof(flashProg) );
	} else if (mode == MODE_MAX) {			// deep sleep mode
		deepsleep();

	//} else {
	}

	// above is all to pick a program. now we run it.
	// XXX - add program launch here
	run_program();
}

///////////////////////////////////////////////////////////////////////////////
// timer/counter0 overflow interrupt handler
///////////////////////////////////////////////////////////////////////////////

ISR(TIMER0_OVF_vect) __attribute__ ((naked));
ISR(TIMER0_OVF_vect) {

	asm("reti"); // return from interrupt
}

///////////////////////////////////////////////////////////////////////////////

// [end-of-file]

